local c_air=minetest.get_content_id("air")
local c_pre_barrel=minetest.get_content_id("dungeon:pre_barrel")
local c_rope=minetest.get_content_id("rope:rope")
local c_maze=minetest.get_content_id("dungeon:maze")
local c_stone=minetest.get_content_id("default:stone")
local data
local va

local function createCuboid(min,max,node)
	node=node or c_stone
	for fi in va:iterp(min,max) do
		data[fi]=node
	end
end

local function barriers(min,max)
	if Random:next(1,2)==1 then
		createCuboid(
			{x=max.x+2,y=min.y,z=min.z},
			{x=max.x+2,y=min.y,z=max.z+4},
			c_stone
		)
		createCuboid(
			{x=min.x,y=min.y,z=max.z+2},
			{x=max.x+2,y=max.y-32,z=max.z+2},
			c_maze
		)
	else
		createCuboid(
			{x=min.x,y=min.y,z=max.z+2},
			{x=max.x+4,y=min.y,z=max.z+2},
			c_stone
		)
		createCuboid(
			{x=max.x+2,y=min.y,z=min.z},
			{x=max.x+2,y=max.y-32,z=max.z+2},
			c_maze
		)
	end
	if Random:next(1,20)<20 then
		createCuboid(
			{x=min.x,y=max.y-32,z=min.z},
			{x=max.x+2,y=max.y-32,z=max.z+2},
			c_maze
		)
	end
end

local function createRope(ix,iy,iz,length)
	for fi=0,length-1,1 do
		data[va:index(ix,iy-fi,iz)]=c_rope
	end
	for fi=1,3,1 do
		data[va:index(ix,iy+fi,iz)]=c_air
	end
end

local function createPassage(ix,iy,iz,xDir,height)
	height=height or 2
	local min={x=ix,y=iy,z=iz}
	local max
	if xDir then
		max={x=ix+1,y=iy+height-1,z=iz}
	else
		max={x=ix,y=iy+height-1,z=iz+1}
	end
	for fi in va:iterp(min,max) do
		data[fi]=c_air
	end
end

local function create8Entrances(min,max,height)
	local ly=min.y+1
	local tmp=math.floor((max.z-min.z)/3)+min.z
	createPassage(max.x+1,ly,tmp,false,height)
	createPassage(min.x,ly,tmp,false,height)
	tmp=math.floor(2*(max.z-min.z)/3)+min.z
	createPassage(max.x+1,ly,tmp,false,height)
	createPassage(min.x,ly,tmp,false,height)
	tmp=math.floor((max.x-min.x)/3)+min.x
	createPassage(tmp,ly,max.z+1,true,height)
	createPassage(tmp,ly,min.z,true,height)
	tmp=math.floor(2*(max.x-min.x)/3)+min.x
	createPassage(tmp,ly,max.z+1,true,height)
	createPassage(tmp,ly,min.z,true,height)
end

local function create4Entrances(min,max,height)
	local ly=min.y+1
	local tmp=math.floor((max.z-min.z)/2)+min.z
	createPassage(max.x+1,ly,tmp,false,height)
	createPassage(min.x,ly,tmp,false,height)
	tmp=math.floor((max.x-min.x)/2)+min.x
	createPassage(tmp,ly,max.z+1,true,height)
	createPassage(tmp,ly,min.z,true,height)
end

local function createRect(min,max,node)
	local ly=min.y+1
	createCuboid({x=min.x,y=ly,z=min.z},{x=min.x,y=max.y,z=max.z},node)
	createCuboid({x=max.x,y=ly,z=min.z},{x=max.x,y=max.y,z=max.z},node)
	createCuboid({x=min.x,y=ly,z=min.z},{x=max.x,y=max.y,z=min.z},node)
	createCuboid({x=min.x,y=ly,z=max.z},{x=max.x,y=max.y,z=max.z},node)
end

local function rooms(min,max)
	if max.x-min.x>10 and max.z-min.z>10 then
		local xWall=math.ceil((max.x-min.x)/Random:next(2,4))+min.x
		local ly=min.y+1
		createCuboid({x=xWall,y=ly,z=min.z+1},{x=xWall,y=ly+2,z=max.z})
		createPassage(xWall,ly,math.floor((max.z-min.z)/3)+min.z,false)
		createPassage(xWall,ly,math.floor(2*(max.z-min.z)/3)+min.z,false)
		local zWall=math.ceil((max.z-min.z)/Random:next(2,4))+min.z
		createCuboid({x=min.x+1,y=ly,z=zWall},{x=max.x,y=ly+2,z=zWall})
		createPassage(math.floor((max.x-min.x)/3)+min.x,ly,zWall,true)
		createPassage(math.floor(2*(max.x-min.x)/3)+min.x,ly,zWall,true)
		rooms(min,{x=xWall,y=max.y,z=zWall})
		rooms({x=xWall,y=min.y,z=zWall},max)
		rooms({x=xWall,y=min.y,z=min.z},{x=max.x,y=max.y,z=zWall})
		rooms({x=min.x,y=min.y,z=zWall},{x=xWall,y=max.y,z=max.z})
	end 
end

local function barrels(min,max,count)
	local lx
	local lz
	for fi=1,count,1 do
		lx=Random:next(min.x,max.x)
		lz=Random:next(min.z,max.z)
		while data[va:index(lx,min.y,lz)]==c_stone do
			lx=Random:next(min.x,max.x)
			lz=Random:next(min.z,max.z)
		end
		data[va:index(lx,min.y,lz)]=c_pre_barrel
	end
end

function tower(min,max)
	local ps1
	local ps2
	for fy=min.y,max.y-36,4 do
		ps1={x=min.x,y=fy,z=min.z}
		rooms(ps1,max)
		barrels({x=ps1.x,y=fy+1,z=ps1.z},max,3)
		ps2={x=max.x+1,y=fy,z=max.z+1}
		createCuboid(ps1,ps2)
		createRope(Random:next(ps1.x+5,ps2.x-6),fy,Random:next(ps1.z+5,ps2.z-6),4)
	end 
	createRect(min,{x=max.x+1,y=max.y-36,z=max.z+1})
	create8Entrances(min,max)
	createCuboid({x=min.x,y=max.y-35,z=min.z},{x=max.x+1,y=max.y-35,z=max.z+1})
end

function platforms(min,max)
	local lx1_3=(max.x-min.x)/3+min.x
	local lx2_3=2*(max.x-min.x)/3+min.x
	local lz1_3=(max.z-min.z)/2+min.z
	local lz2_3=2*(max.z-min.z)/2+min.z
	local ps1={x=min.x,y=min.y,z=min.z}
	local ps2={x=max.x+1,y=min.y,z=max.z+1}
	createCuboid(ps1,ps2)
	barrels({x=ps1.x,y=min.y+1,z=ps1.z},ps2,1)
	for fy=min.y+5,max.y-36,4 do
		ps1={x=Random:next(min.x,lx1_3),y=fy,z=Random:next(min.z,lz1_3)}
		ps2={x=Random:next(lx2_3,max.x),y=fy,z=Random:next(lz2_3,max.z)}
		createCuboid(ps1,ps2)
		createRope(Random:next(lx1_3,lx2_3),fy,Random:next(lz1_3,lz2_3),4)
		barrels({x=ps1.x,y=fy+1,z=ps1.z},ps2,1)
	end 
end

function weirdBuilding(min,max)
	local lx1_3=(max.x-min.x)/3+min.x
	local lx2_3=2*(max.x-min.x)/3+min.x
	local lz1_3=(max.z-min.z)/3+min.z
	local lz2_3=2*(max.z-min.z)/3+min.z
	local ps1={x=min.x,y=min.y,z=min.z}
	local ps2={x=max.x+1,y=min.y+3,z=max.z+1}
	rooms(ps1,ps2)
	createCuboid(ps1,{x=ps2.x,y=min.y,z=ps2.z})
	createRect(ps1,ps2)
	createCuboid({x=ps1.x,y=ps1.y+4,z=ps1.z},{x=ps2.x,y=ps1.y+4,z=ps2.z})
	barrels({x=ps1.x,y=ps1.y+1,z=ps1.z},ps2,2)
	local rx
	local rz
	for fy=min.y+5,max.y-37,5 do
		ps1={x=Random:next(min.x,lx1_3-1),y=fy,z=Random:next(min.z,lz1_3-1)}
		ps2={x=Random:next(lx2_3+5,max.x),y=fy+3,z=Random:next(lz2_3+5,max.z)}
		rooms(ps1,ps2)
		createCuboid(ps1,{x=ps2.x,y=ps1.y,z=ps2.z})
		createRect(ps1,ps2)
		createCuboid({x=ps1.x,y=ps1.y+4,z=ps1.z},{x=ps2.x,y=ps1.y+4,z=ps2.z})
		rx=Random:next(lx1_3,lx2_3)
		rz=Random:next(lz1_3,lz2_3)
		createRope(rx,fy,rz,5)
		barrels({x=ps1.x,y=fy+1,z=ps1.z},ps2,2)
	end 
	create8Entrances(min,max)
end

function pyramid(min,max)
	local ps1
	local ps2
	local ld=0
	for fy=min.y,max.y-36,4 do
		ps1={x=min.x+ld,y=fy,z=min.z+ld}
		ps2={x=max.x+1-ld,y=fy+3,z=max.z+1-ld}
		rooms(ps1,ps2)
		createCuboid(ps1,{x=ps2.x,y=ps1.y,z=ps2.z})
		createRope(Random:next(ps1.x+5,ps2.x-6),fy,Random:next(ps1.z+5,ps2.z-6),4)
		createRect(ps1,ps2)
		barrels({x=ps1.x,y=fy+1,z=ps1.z},ps2,3)
		ld=ld+1
	end 
	createCuboid({x=ps1.x,y=max.y-35,z=ps1.z},{x=ps2.x,y=max.y-35,z=ps2.z})
	create8Entrances(min,max)
end

function upsideDownPyramid(min,max)
	local ps1
	local ps2
	local ld=19
	createCuboid(min,{x=max.x+1,y=min.y,z=max.z+1})
	for fy=min.y,max.y-36,4 do
		ld=ld-1
		ps1={x=min.x+ld,y=fy,z=min.z+ld}
		ps2={x=max.x+1-ld,y=fy+3,z=max.z+1-ld}
		rooms(ps1,ps2)
		createCuboid(ps1,{x=ps2.x,y=ps1.y,z=ps2.z})
		createRope(Random:next(ps1.x+5,ps2.x-6),fy,Random:next(ps1.z+5,ps2.z-6),4)
		createRect(ps1,ps2)
		barrels({x=ps1.x,y=fy+1,z=ps1.z},ps2,3)
	end 
	create4Entrances({x=min.x+18,y=min.y,z=min.z+18},{x=max.x-18,y=max.y,z=max.z-18})
	createCuboid({x=ps1.x,y=max.y-35,z=ps1.z},{x=ps2.x,y=max.y-35,z=ps2.z})
end

local function towerWithPool(min,max)
	tower(min,max)
	local height=min.y+4*Random:next(1,5)
	createRect(
		{x=min.x+30,y=min.y,z=min.z+30},
		{x=min.x+50,y=height,z=min.z+50},
		minetest.get_content_id("default:glass")
	)
	createCuboid(
		{x=min.x+31,y=min.y+1,z=min.z+31},
		{x=min.x+49,y=height,z=min.z+49},
		minetest.get_content_id("default:water_source")
	)
	createCuboid(
		{x=min.x+31,y=height+1,z=min.z+31},
		{x=min.x+49,y=height+3,z=min.z+49},
		c_air
	)
	for fy=min.y+3,height-2,4 do
		create4Entrances(
			{x=min.x+30,y=fy,z=min.z+30},
			{x=min.x+49,y=height,z=min.z+49},
			3
		)
	end
end

local function towerWithLava(min,max)
	tower(min,max)
	local ps1
	local ps2
	local ld=0
	local height=min.y+4*Random:next(1,5)
	local lc=minetest.get_content_id("default:obsidian_glass")
	local lc2=minetest.get_content_id("default:lava_source")
	for fy=min.y,height,4 do
		ps1={x=min.x+30+ld,y=fy,z=min.z+30+ld}
		ps2={x=min.x+50-ld,y=fy+4,z=min.z+50-ld}
		createRect(ps1,ps2,lc)
		createCuboid(
			{x=min.x+31+ld,y=fy,z=min.z+31+ld},
			{x=min.x+49-ld,y=fy+4,z=min.z+49-ld},
			lc2
		)
		ld=ld+1
	end 
	createCuboid(
		{x=min.x+31,y=min.y,z=min.z+31},
		{x=min.x+49,y=min.y,z=min.z+49}
	)
	createCuboid(
		{x=min.x+30+ld,y=height+5,z=min.z+30+ld},
		{x=min.x+50-ld,y=height+7,z=min.z+50-ld},
		c_air
	)
end

local generate={
	tower,
	tower,
	tower,
	tower,
	tower,
	tower,
	tower,
	tower,
	platforms,
	weirdBuilding,
	pyramid,
	upsideDownPyramid,
	towerWithPool,
	towerWithLava,
}

--The size of the areas is 80x80x80
minetest.register_on_generated(
	function(minp,maxp)
		if minp.y>-200 then
			return
		end
		local vm,emin,emax=minetest.get_mapgen_object("voxelmanip")
		local area=vm:read_from_map(emin,emax)
		data=vm:get_data()
		va=VoxelArea:new({MinEdge=emin,MaxEdge=emax})
		emin.x=emin.x+17
		emax.x=emax.x-17
		emin.z=emin.z+17
		emax.z=emax.z-17
		createCuboid(emin,{x=emax.x+2,y=emax.y-33,z=emax.z+2},c_air)
		barriers(emin,emax)
		generate[Random:next(1,#generate)](emin,emax)
		vm:set_data(data)
		vm:set_lighting(vm:get_light_data())
		vm:write_to_map(true)
	end
)

local food={
	"default:apple","default:blueberries", 
	"default:apple","default:blueberries", 
	"default:apple","default:blueberries", 
	"farming:bread","flowers:mushroom_brown",
	"farming:bread","flowers:mushroom_brown",
	"mobs:meat_raw","mobs:meat",
	"mobs:meat_raw","mobs:meat",
	"zombie:rotten_flesh"
}

local ores={
	"default:coal_lump",
	"default:coal_lump",
	"default:bronze_ingot",
	"default:bronze_ingot",
	"default:copper_ingot",
	"default:copper_ingot",
	"default:copper_ingot",
	"default:gold_ingot",
	"default:gold_ingot",
	"default:steel_ingot",
	"default:steel_ingot",
	"default:tin_ingot",
	"default:diamond",
	"default:mese_crystal"
}

local wood={
	"default:wood",
	"default:junglewood",
	"default:pine_wood",
	"default:acacia_wood",
	"default:aspen_wood",
	"default:stick"
}

local misc={
	{
		"default:cactus",
		"default:papyrus",
		"default:clay",
		"default:flint",
		"default:obsidian_shard",
		"default:glass",
		"default:ice",
		"default:snow",
	},
	{
		"flowers:rose",
		"flowers:tulip",
		"flowers:dandelion_yellow",
		"flowers:chrysanthemum_green",
		"flowers:geranium",
		"flowers:viola",
		"flowers:dandelion_white",
		"flowers:tulip_black",
	},
	{
		"farming:cotton",
		"farming:cotton",
	}
}

local function fill(inv)
	inv:add_item("main",ItemStack(food[Random:next(1,#food)]))
	inv:add_item("main",ItemStack(ores[Random:next(1,#ores)]))
	inv:add_item("main",ItemStack(wood[Random:next(1,#wood)]))
	local tmp=Random:next(1,#misc)
	inv:add_item("main",ItemStack(misc[tmp][Random:next(1,#misc[tmp])]))
end

local function spawn(ps)
	local objs=minetest.get_objects_inside_radius(ps,10)
	local count=0
	local ent=nil
	for _, obj in ipairs(objs) do
		ent=obj:get_luaentity()
		if ent and ent.name and ent.name=="mobs_dungeon:guard" then
			count=count+1
		end
	end
	if count<3 then
		for fi=count,4,1 do
			minetest.add_entity({x=ps.x,y=ps.y+1,z=ps.z},"mobs_dungeon:guard")
		end
	end
end

minetest.register_abm(
	{
		nodenames={"dungeon:barrel"},
		interval=10.0,
		chance=6,
		catch_up=true,
		action=function(ps,node)
			local inv=minetest.get_inventory({type="node",pos=ps})
			if inv:is_empty("main") then
				fill(minetest.get_inventory({type="node",pos=ps}))
			end
			spawn(ps)
		end
	}
)

minetest.register_abm(
	{
		nodenames={"dungeon:pre_barrel"},
		interval=1.0,
		chance=1,
		catch_up=true,
		action=function(ps,node)
			minetest.set_node(ps,{name="dungeon:barrel"})
			fill(minetest.get_inventory({type="node",pos=ps}))
			spawn(ps)
		end
	}
)
